// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// WorldBuilderView.cpp : implementation of the CMainView class
//
#include "stdafx.h"
#include "WorldBuilder.h"
#include "MainFrm.h"
#include "MainView.h"
#include "WorldBuilderDoc.h"
#include "ActionManager.h"
#include "AnimationManager.h"
#include "Ray.h"
#include "Sphere.h"
#include "Line.h"
#include "Quad.h"
#include "GeometryNode.h"
#include "LightManager.h"
#include "ShaderProgram.h"
#include "Color.h"
#include "DummyWindow.h"
#include "TextureTransform.h"
#include "AABB.h" // remove


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glew32.lib")


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =


#include "SOIL.h"

class TexturedQuad : public Drawable
{
    GLuint m_textures[2];

public:

    TexturedQuad()
    {
        const char* fileNames[] = { "texture.png", "transparent.png" };
        for(int i = 0; i < 2; ++i)
        {
            m_textures[i] = SOIL_load_OGL_texture(
                fileNames[i], SOIL_LOAD_RGBA, SOIL_CREATE_NEW_ID,
                SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB 
                | SOIL_FLAG_COMPRESS_TO_DXT);
        }
    }

    void draw(View3d* view) const override
    {
        glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
        glEnable(GL_LIGHTING);     

        glEnable(GL_COLOR_MATERIAL);
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); 
        Grey128();

        glActiveTexture(GL_TEXTURE0);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_textures[0]);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

        glActiveTexture(GL_TEXTURE0 + 1);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_textures[1]);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

        glActiveTexture(GL_TEXTURE0 + 2);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_textures[0]);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR);
        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

        glBegin(GL_QUADS);

        glMultiTexCoord2f(GL_TEXTURE0 + 0, 0.0f, 0.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 1, 0.0f, 0.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0f, 0.0f); 
        glNormal3f(0, 0, 1);
        glVertex2f(0, 0);
        
        glMultiTexCoord2f(GL_TEXTURE0 + 0, 1.0f, 0.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 1, 1.0f, 0.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0f, 0.0f); 
        glNormal3f(0, 0, 1);
        glVertex2f(1, 0);
        
        glMultiTexCoord2f(GL_TEXTURE0 + 0, 1.0f, 1.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 1, 1.0f, 1.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0f, 0.0f); 
        glNormal3f(0, 0, 1);
        glVertex2f(1, 1);
        
        glMultiTexCoord2f(GL_TEXTURE0 + 0, 0.0f, 1.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 1, 0.0f, 1.0f); 
        glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0f, 0.0f); 
        glNormal3f(0, 0, 1);
        glVertex2f(0, 1);
        
        glEnd();

        glPopAttrib();
    }
};



// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// User-defined messages
// See this http://vcfaq.mvps.org/mfc/12.htm

// The main reason we use user-defined messages is because it's the easiest
// way to update the controls from worker threads

#define WM_ED3D_UPDATE_GIZMO_COMBOS (WM_APP + 1)

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

// TODO add virtual destructors!!

//gx::Vec3 unproject( int x, int y, double z);


// TODO highlight the tile under the selected block

// DETECTING SINGLE CLICKS
// Most applications deal with single clicks in the simplest 
// possible way, interpreting a BUTTONDOWN message as a single 
// click. However, this doesn't suffice when we want to consider
// double click events as well, because in that case for each
// double click action we would be responding to both events
// (single click and double click). Something a bit more complex
// is needed. Unfortunately, MFC doesn't make our life any easier.
// In case of dbl. clk., MFC sends the following messages:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) DOUBLECLICK
//   (4) BUTTONUP
// The longest lapse between the two button up events is given by 
// ::GetDoubleClickTime() (milliseconds). We use this fact in order
// to differentiate singleclicks from double clicks. On (2) we 
// start a timer set to ::GetDoubleClickTime() milliseconds. 
// If (3) happens, we kill the timer and handle a double click
// event. If the timer expires, we kill it as well and handle a 
// single click event. A side effect of this approach is that single 
// clicks are kind of slow (::GetDoubleClickTime() is around 400 ms),
// but this// is the usual way to deal with this in MFC apps (see
// the Solution Explorer of VS for instance). Things are complicated 
// further by the fact that two consecutive clicks are not considered 
// a double click (i.e., (3) is not sent) if the mouse cursor moves 
// in between (even 1 pixel). In that case MFC sends the following 
// sequence of messages:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) BUTTONDOWN
//   (4) BUTTONUP
// We interpret this in the same way as VS: as a single click event.
// Another possibility is three consecutive clicks, the first one on 
// a different cursor position. In that case MFC sends:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) BUTTONDOWN
//   (4) BUTTONUP
//   (5) DOUBLECLICK
//   (6) BUTTONUP
// We interpret this in the same way as VS: as a single click in the 
// first cursor position followed by a double click on the second cursor 
// position To implement this convoluted logic easily we use a finite state 
// machine


// TODO "face hover" should have precedence over "hover on another block", 
// even if that other block is closer, because otherwise we can't act on
// a face that has an adjacent block (the opposites is always possible 
// through careful aiming)


// CMainView

IMPLEMENT_DYNCREATE(CMainView, CView)

BEGIN_MESSAGE_MAP(CMainView, CView)
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_WM_ERASEBKGND()
    ON_WM_KEYDOWN()
    ON_WM_KEYUP()
    ON_WM_TIMER()
    ON_WM_CHAR()
    ON_WM_MOUSEMOVE()
    ON_WM_MOUSEWHEEL()
    ON_WM_LBUTTONUP()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_MBUTTONUP()
    ON_WM_MBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONDBLCLK()
    ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
    ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
    ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
    ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
    ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
    ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
    ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
    ON_COMMAND(ID_EDIT_CUT, OnEditCut)
    ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
    ON_COMMAND(ID_MANIP_CAM, OnManipCam)
    ON_UPDATE_COMMAND_UI(ID_MANIP_CAM, OnUpdateManipCam)
    ON_COMMAND(ID_MANIP_OBJ, OnManipObj)
    ON_UPDATE_COMMAND_UI(ID_MANIP_OBJ, OnUpdateManipObj)
    ON_COMMAND(ID_MANIP_TRANSL, OnManipTransl)
    ON_UPDATE_COMMAND_UI(ID_MANIP_TRANSL, OnUpdateManipTransl)
    ON_COMMAND(ID_MANIP_ROT, OnManipRot)
    ON_UPDATE_COMMAND_UI(ID_MANIP_ROT, OnUpdateManipRot)
    ON_COMMAND(ID_MANIP_SCALE, OnManipScale)
    ON_UPDATE_COMMAND_UI(ID_MANIP_SCALE, OnUpdateManipScale)
    ON_COMMAND(ID_MANIP_PIVOT, OnManipPivot)
    ON_CBN_SELENDOK(ID_MANIP_PIVOT, OnManipPivot)
    ON_UPDATE_COMMAND_UI(ID_MANIP_PIVOT, OnUpdateManipPivot)
    ON_COMMAND(ID_MANIP_FRAME, OnManipFrame)
    ON_CBN_SELENDOK(ID_MANIP_FRAME, OnManipFrame)
    ON_UPDATE_COMMAND_UI(ID_MANIP_FRAME, OnUpdateManipFrame)
    ON_CBN_CLOSEUP(ID_MANIP_FRAME, OnManipComboCloseup)
    ON_CBN_CLOSEUP(ID_MANIP_PIVOT, OnManipComboCloseup)
    ON_COMMAND(ID_VIEW_PERSPECTIVE, OnViewPerspective)
    ON_UPDATE_COMMAND_UI(ID_VIEW_PERSPECTIVE, OnUpdateViewPerspective)
    ON_COMMAND(ID_VIEW_CLEAR, OnViewClear)
    ON_MESSAGE(WM_ED3D_UPDATE_GIZMO_COMBOS, OnUpdateGizmoCombos)
    ON_COMMAND(ID_SHADERSMENU_ADDEMPTYSHADER, OnNewEmptyShader)
    ON_COMMAND(ID_SHADERMENU_LOADVERTEXSHADER, OnLoadVertexShader)
    ON_COMMAND(ID_SHADERMENU_LOADFRAGMENTSHADER, OnLoadFragmentShader)
    ON_COMMAND(ID_SHADERMENU_NEWVERTEXSHADER, OnNewVertexShader)
    ON_COMMAND(ID_SHADERMENU_NEWFRAGMENTSHADER, OnNewFragmentShader)
    ON_COMMAND(ID_SHADERMENU_PRINTUNIFORMVARIABLES, OnPrintUniformVariables)
    ON_COMMAND(ID_TEXTUREMANAGER_ADDTEXTURE, OnAddTexture)
    ON_COMMAND(ID_TEXTURELAYER_ADDLAYERAFTER, OnAddTextureLayerAfter)
    ON_COMMAND(ID_NODE_DELETE, OnNodeDelete)
    ON_COMMAND(ID_ADDTEXTURETRANSFORM_LAYER0, OnNodeAddTextureTransform0)
    ON_UPDATE_COMMAND_UI(ID_ADDTEXTURETRANSFORM_LAYER0, OnUpdateNodeAddTextureTransform0)
    ON_COMMAND(ID_REMOVETEXTURETRANSFORM_LAYER0, OnNodeRemoveTextureTransform0)
    ON_UPDATE_COMMAND_UI(ID_REMOVETEXTURETRANSFORM_LAYER0, OnUpdateNodeRemoveTextureTransform0)
    ON_COMMAND(ID_SELECTTEXTURETRANSFORM_LAYER0, OnNodeSelectTextureTransform0)
    ON_UPDATE_COMMAND_UI(ID_SELECTTEXTURETRANSFORM_LAYER0, OnUpdateNodeSelectTextureTransform0)
    ON_COMMAND(ID_SELECTTEXTURETRANSFORM_LAYER1, OnNodeSelectTextureTransform1)
    ON_UPDATE_COMMAND_UI(ID_SELECTTEXTURETRANSFORM_LAYER1, OnUpdateNodeSelectTextureTransform1)
    ON_COMMAND(ID_SELECTTEXTURETRANSFORM_LAYER2, OnNodeSelectTextureTransform2)
    ON_UPDATE_COMMAND_UI(ID_SELECTTEXTURETRANSFORM_LAYER2, OnUpdateNodeSelectTextureTransform2)
    ON_COMMAND(ID_SELECTTEXTURETRANSFORM_LAYER3, OnNodeSelectTextureTransform3)
    ON_UPDATE_COMMAND_UI(ID_SELECTTEXTURETRANSFORM_LAYER3, OnUpdateNodeSelectTextureTransform3)
    ON_COMMAND(ID_ADDTEXTURETRANSFORM_LAYER1, OnNodeAddTextureTransform1)
    ON_UPDATE_COMMAND_UI(ID_ADDTEXTURETRANSFORM_LAYER1, OnUpdateNodeAddTextureTransform1)
    ON_COMMAND(ID_REMOVETEXTURETRANSFORM_LAYER1, OnNodeRemoveTextureTransform1)
    ON_UPDATE_COMMAND_UI(ID_REMOVETEXTURETRANSFORM_LAYER1, OnUpdateNodeRemoveTextureTransform1)
    ON_COMMAND(ID_ADDTEXTURETRANSFORM_LAYER2, OnNodeAddTextureTransform2)
    ON_UPDATE_COMMAND_UI(ID_ADDTEXTURETRANSFORM_LAYER2, OnUpdateNodeAddTextureTransform2)
    ON_COMMAND(ID_REMOVETEXTURETRANSFORM_LAYER2, OnNodeRemoveTextureTransform2)
    ON_UPDATE_COMMAND_UI(ID_REMOVETEXTURETRANSFORM_LAYER2, OnUpdateNodeRemoveTextureTransform2)
    ON_COMMAND(ID_ADDTEXTURETRANSFORM_LAYER3, OnNodeAddTextureTransform3)
    ON_UPDATE_COMMAND_UI(ID_ADDTEXTURETRANSFORM_LAYER3, OnUpdateNodeAddTextureTransform3)
    ON_COMMAND(ID_REMOVETEXTURETRANSFORM_LAYER3, OnNodeRemoveTextureTransform3)
    ON_UPDATE_COMMAND_UI(ID_REMOVETEXTURETRANSFORM_LAYER3, OnUpdateNodeRemoveTextureTransform3)
END_MESSAGE_MAP()

// CMainView construction/destruction

CMainView::CMainView()
: m_leftBtnClickState(ClickCycleStates::IDLE),
  m_rightBtnClickState(ClickCycleStates::IDLE),
  m_leftBtnDown(false),
  m_draggable(NULL)
{
}




CMainView::~CMainView()
{
    m_gizmoManager.setView(NULL);
}

BOOL CMainView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs

    return __super::PreCreateWindow(cs);
}

void CMainView::OnClose() 
{
    MakeCurrent();
    wglDeleteContext(m_hRC);
    CWnd::OnClose();
}

// CMainView drawing

BOOL CMainView::OnEraseBkgnd(CDC* /*pDC*/)
{
    return FALSE;
}

void CMainView::drawGrid() const
{
    glPushAttrib(GL_ENABLE_BIT | GL_LINE_BIT);
    glLineWidth(1.f);
        
    glDisable(GL_LIGHTING);

        makeBrighter(getBackgroundColor(), 5)();

        double absCamY = fabs(m_cameraMat.translation.y);
        int N = std::min(
                    std::max(25, int(25 * absCamY)),
                    200);

        int camX = int(m_cameraMat.translation.x);
        int camZ = int(m_cameraMat.translation.z);

        glBegin(GL_LINES);
            for(int i = -N; i < N; ++i) 
            {
                glVertex3i(camX + i, 0, camZ-N);
                glVertex3i(camX + i, 0, camZ+N);
            }
            
            for(int i = -N; i < N; ++i) 
            {
                glVertex3i(camX + -N, 0, camZ+i);
                glVertex3i(camX + N, 0, camZ+i);
            }
        glEnd();

    glPopAttrib();
}

void drawScreenAxes(View3d * view, const gx::Mat3 & axes, 
    int w, int h, double brightness, double length, double thickness)
{
    // A point on the lower left corner of the screen

    gx::Vec3 p = view->unproject(
        ScreenPoint(int(w * .05), int(h * .95)), 0.5);
  
    double factor = view->computeConstantScaleFactor(p);

    glPushMatrix();

    glTranslated(p.x, p.y, p.z);
    
    double m[16];
    gx::Mat4().make_rot(axes).to_array(m);
    glMultMatrixd(m);

    double scale = length * factor;
    glScaled(scale, scale, scale);
    

    glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT);
    

        glLineWidth(float(thickness));
        glDisable(GL_LIGHTING);
        
        glDisable(GL_FOG);
        
        glDisable(GL_DEPTH_TEST);

        glBegin(GL_LINES);
            glColor3d(brightness, 0., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(1., 0., 0.);
            
            glColor3d(0., brightness, 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 1., 0.);

            glColor3d(0., 0., brightness);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 0., 1.);
        glEnd();

    glPopAttrib();
    glPopMatrix();
}

void drawAxes(bool balls=false)
{
    glPushMatrix();
    glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT);
        glScaled(.2, .2, .2);
        glLineWidth(1.5f);
        glDisable(GL_LIGHTING);
        glBegin(GL_LINES);
            glColor3d(1., 0., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(1., 0., 0.);
            
            glColor3d(0., 1., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 1., 0.);

            glColor3d(0., 0., 1.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 0., 1.);
        glEnd();

        if(balls)
        {
            glPushAttrib(GL_POINT_BIT);
            glPointSize(10.f);
            glBegin(GL_POINTS);
                glColor3d(1., 0.5, 0.5);
                glVertex3d(1., 0., 0.);
        
                glColor3d(.5, 1., 0.5);
                glVertex3d(0., 1., 0.);

                glColor3d(.5, .5, 1.);
                glVertex3d(0., 0., 1.);
            glEnd();
            glPopAttrib();
        }
    glPopAttrib();
    glPopMatrix();
}


extern const GLubyte stippleMask[17][128];

void CMainView::invalidateImpl()
{
    if((HWND)*this)
    {
        Invalidate();
    }
}

namespace
{

void glColor(float r, float g, float b, float a)
{   
    glColor4f(r, g, b, a);
}

} // anonymous namespace


void CMainView::OnDraw(CDC* /*pDC*/)
{
    // We assume that we are drawing because something changing in the scene,
    // which means that the object under the mouse cursor could have changed
    // TODO find a more elegant and efficient way to do this
    // TODO put this inside a no-args overload of updateHover
    CPoint point;
    ::GetCursorPos(&point);
    ScreenToClient(&point);
    
    MakeCurrent();

    // Notify base class that we are about to begin rendering
    beginRender();

    // Clear depth buffer and background
    glClear(GL_DEPTH_BUFFER_BIT);
    drawBackground();
    
    // TODO extract this into method drawGL()

    setProjectionMatrix();

    setModelViewMatrix();
    
    getWorld().getLightManager().updateLighting();
    
    drawGrid();

    int w, h;
    getDims(w, h);
    drawScreenAxes(this, gx::eye, w, h, 1., .2, 1.5);
    

    //if(SelectionManager::instance().isNodeSelected())
    //{
    //    drawScreenAxes(this,
    //        SelectionManager::instance().getSelectedNode()
    //            ->getGlobalTransform().rotation, 
    //        rc.Width(), rc.Height(),
    //        1., .25, 2.5);
    //}
    //drawAxes();

    Selectable* sel = getUi().getSelectionManager().getSelection();
    GeometryNode* selNode = dynamic_cast<GeometryNode*>(sel);
    Node* hoverNode = dynamic_cast<Node*>(m_hoverItem);
    Color yellow_(ColorIds::Yellow);
    if(selNode != NULL && selNode != hoverNode && selNode->isBounded())
    {
        makeDarker(makeDarker(yellow_))();
        AABB aabb = selNode->getAabbLocal();
        selNode->drawAabb();
    }

    if(hoverNode && hoverNode->isBounded())
    {
        yellow_();
        hoverNode->drawAabb();
        
    }

    DrawableSet::iterator it = m_drawables.begin();
    for(; it != m_drawables.end(); ++it)
    {
        Transformable* transformable = dynamic_cast<Transformable*>(it->get());
        if(transformable)
        {
            glPushMatrix();
            gx::Mat4 mat = transformable->getGlobalTransform();
            double m[16];
            glMultMatrixd(mat.to_array(m));
            
            (*it)->draw(this);
        
            glPopMatrix();
        }
        else
        {
            (*it)->draw(this);
        }

    }

    // Notify base class that we're done rendering
    endRender();

    SwapBuffers(GetDC()->m_hDC);
}


void CMainView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
    CMenu mainMenu;
    VERIFY(mainMenu.LoadMenu(IDR_MAINFRAME));
    
    CMenu * blockMenu = mainMenu.GetSubMenu(3);
    ASSERT(blockMenu != NULL);

    theApp.GetContextMenuManager()->ShowPopupMenu(
        blockMenu->GetSafeHmenu(), point.x, point.y, this, TRUE);
}


// CMainView diagnostics

#ifdef _DEBUG
void CMainView::AssertValid() const
{
    __super::AssertValid();
}

void CMainView::Dump(CDumpContext& dc) const
{
    __super::Dump(dc);
}

//CWorldBuilderDoc* CMainView::GetDocument() const // non-debug version is inline
//{
// ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWorldBuilderDoc)));
// return (CWorldBuilderDoc*)m_pDocument;
//}

#endif //_DEBUG



// CMainView message handlers

int CMainView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if(__super::OnCreate(lpCreateStruct) == -1)
        return -1;


    // TODO do this somewhere else (probably ctor. of App)
    static bool glutInitialized = false;
    if(!glutInitialized) 
    {
        int argc = 0;
        char * argv[] = { "dummy" };
        glutInit(&argc, argv);
        glutInitialized = true;
    }

    CDummyWindow dummywindow;
    dummywindow.DoModal();

    PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB 
        = dummywindow.wglChoosePixelFormatARB;
    
    //Get a DC for the Client Area
    CDC* dc = GetDC();

    PIXELFORMATDESCRIPTOR pfd = getStandardPixelFormatDescriptor();

    int  valid;
    UINT numFormats;
    float fAttributes[] = {0,0};

    int iAttributes[] =
    {
        WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
        WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
        WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
        WGL_COLOR_BITS_ARB,24,
        WGL_ALPHA_BITS_ARB,8,
        WGL_DEPTH_BITS_ARB,16,
        WGL_STENCIL_BITS_ARB,0,
        WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
        WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
        WGL_SAMPLES_ARB,4,
        0,0
    }; 

    int pixelFormat;
    // First We Check To See If We Can Get A Pixel Format For 4 Samples
    if(!wglChoosePixelFormatARB(dc->GetSafeHdc(),iAttributes,
                                    fAttributes,1,&pixelFormat,&numFormats))
    {
        iAttributes[19] = 2;  // 2 samples
        VERIFY(wglChoosePixelFormatARB(dc->GetSafeHdc(),iAttributes,
                                       fAttributes,1,&pixelFormat,&numFormats));
    }

    // Once we obtain the closest match, we call the SetPixelFormat function
    // which sets the pixel format of the specified device context to the format specified by the index. 
    if(SetPixelFormat(dc->GetSafeHdc(), pixelFormat, &pfd) == FALSE) 
    {
        return -1;
    }

    //Create Rendering Context
    m_hRC = wglCreateContext (dc->GetSafeHdc());

    //Make the RC Current
    if(wglMakeCurrent(dc->GetSafeHdc(), m_hRC)==false)
    {
        MessageBox(_T("Error making RC Current"));
        return -1;
    }

    
    // TODO extract method "initGL"

    glEnable(GL_DEPTH_TEST);
    


    // We don't use blending at the moment but just in case...
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
    

    glClearColor(getBackgroundColor()[0], getBackgroundColor()[1], getBackgroundColor()[2], getBackgroundColor()[3]);

    glEnable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_NORMALIZE);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

    glEnable (GL_FOG);
    glFogf(GL_FOG_START, 10.f);
    glFogf(GL_FOG_END, 50.f);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogfv(GL_FOG_COLOR, getBackgroundColor().to_array());
    glHint(GL_FOG_HINT, GL_NICEST);
    

    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    

    glShadeModel(GL_SMOOTH);
    

    setColorCallback(&glColor);


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // init glew to manage extesions
    if (glewInit() != GLEW_OK) 
    {
      AfxMessageBox(_T("Error in glewInit()"), MB_ICONERROR + MB_OK );
      return -1;
    }

    // make sure opengl 2 is supported
    if (glewIsSupported("GL_VERSION_2_0"))
    {
        dbg("Ready for OpenGL 2.0");
    }
    else 
    {
        AfxMessageBox(L"OpenGL 2.0 not supported\n");
        exit(-1);
    }

    // multisamble, full-scene anti-aliasing
    if (!GLEW_ARB_multisample) 
    {
        AfxMessageBox(_T("GLEW_multisample not avaliable!"), 
                      MB_ICONERROR + MB_OK );
        return -1;
    }

    // multitexturing
    if (!GLEW_ARB_multitexture) 
    {
        AfxMessageBox(_T("GLEW_multitexture not avaliable!"), 
                        MB_ICONERROR + MB_OK );
        return -1;
    }
    
    // GLEW_texture_env_combine
    if (!GLEW_ARB_texture_env_combine) 
    {
        AfxMessageBox(_T("GLEW_texture_env_combine not avaliable!"), 
                        MB_ICONERROR + MB_OK );
        return -1;
    }

    // activate multisampling
    glEnable(GL_MULTISAMPLE);
    glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
    glGetError(); // The previous hint fails in my system; this clears the error
    

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    //addDrawable(ref<Drawable>(new TexturedQuad));
    //addDrawable(ref<Drawable>(new VboTest));
    return 0;
}

void CMainView::resize(int w, int h)
{
    glViewport(0, 0, w, h);
}

void CMainView::OnSize(UINT nType, int cx, int cy)
{
    __super::OnSize(nType, cx, cy);

    MakeCurrent();
    resize(cx, (cy == 0) ? 1 : cy);
}


void CMainView::MakeCurrent()
{
    wglMakeCurrent(GetDC()->m_hDC, m_hRC);
}



// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -
// Implementation of View3d virtual members
// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -

gx::Vec3 CMainView::unprojectImpl(const ScreenPoint & p, double z) const
{
    GLfloat wx, wy, wz;
    GLdouble cx, cy, cz;
    
    GLdouble mv[16];
    glGetDoublev( GL_MODELVIEW_MATRIX, mv );

    GLdouble proj[16];
    glGetDoublev( GL_PROJECTION_MATRIX, proj );

    GLint vp[4];
    glGetIntegerv( GL_VIEWPORT, vp );

    wx = (GLfloat)p.x;
    wy = (GLfloat)vp[3] - (float)p.y;
    wz = (GLfloat)z;
// glReadPixels( x, (int)wy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &wz );

    gluUnProject( wx, wy, wz, mv, proj, vp, &cx, &cy, &cz );

    return gx::Vec3(cx, cy, cz);
}


ScreenPoint CMainView::projectImpl(const gx::Vec3 & v) const
{
    CRect rc;
    GetClientRect(rc);
    int h = rc.Height();

    GLfloat wx, wy, wz;
    GLdouble cx, cy, cz;
    
    GLdouble mv[16];
    glGetDoublev( GL_MODELVIEW_MATRIX, mv );

    GLdouble proj[16];
    glGetDoublev( GL_PROJECTION_MATRIX, proj );

    GLint vp[4];
    glGetIntegerv( GL_VIEWPORT, vp );

    double a[3];
    gluProject(v.x, v.y, v.z, mv, proj, vp, a, a + 1, a + 2);
    ScreenPoint result((int)a[0], (int)a[1]);
    result.y = h - result.y;
    return result;
}

gx::Mat4 CMainView::getCameraMatrixImpl() const
{
    return m_cameraMat;
}

void CMainView::getDimsImpl(int & w, int & h) const
{
    CRect rc;
    GetClientRect(rc);
    w = rc.Width();
    h = rc.Height();
}





// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -



// ****************************************************************************
// ****************************************************************************
// DEALING WITH MOUSE MESSAGES

// Dealing with the mouse wheel message

BOOL CMainView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    // TODO make SHIFT + Mouse Wheel => Roll
    
    cameraDolly(-zDelta * 5e-3);
    return __super::OnMouseWheel(nFlags, zDelta, pt);
}

void CMainView::onMButtonDrag(UINT /*iniFlags*/, CPoint iniPoint, 
    UINT currFlags, CPoint currPoint)
{
    onCameraDrag(iniPoint, currFlags, currPoint);
}

// Dealing with the mouse move message

void CMainView::OnMouseMove(UINT nFlags, CPoint point)
{
    // For some reason this is not always the case
    bool mouseActuallyMoved = !(point == m_point);
    
    if(m_leftBtnDown && mouseActuallyMoved)
    {
        onLButtonDrag(m_flags, m_point, nFlags, point);
    }
    else if(m_middleBtnDown && mouseActuallyMoved) 
    {
        onMButtonDrag(m_flags, m_point, nFlags, point);
    }
    else
    {
        // Don't change hover during a drag op.
        int subName;
        Pickable* item = doPickTest(point, subName);
        bool hoverChanged = m_hoverItem != item 
                       || m_hoverItemPart != subName;
        m_hoverItemPart = subName;
        m_hoverItem = item;

        if(hoverChanged)
        {
            invalidate();
        }

    }
    __super::OnMouseMove(nFlags, point);
}


void CMainView::onLButtonDrag(UINT iniFlags, CPoint iniPoint,
    UINT /*currFlags*/, CPoint currPoint)
{
    //dbg("DR");
    // If the mouse has moved between down and up it's not a 
    // single click. We must set this variable to false or the
    // single click timer will be activated when the left button
    // up message is received
    m_singleClickPossible = false;

    // Dragging means that left button is down and the mouse 
    // has moved at least a pixel

    bool firstDragEvent = m_leftBtnClickState != ClickCycleStates::DRAGGING;
    //m_dragging = true;

    Draggable* draggable = dynamic_cast<Draggable*>(m_hoverItem);
    if(draggable)
    {
        m_leftBtnClickState = ClickCycleStates::DRAGGING;
        if(firstDragEvent)
        {
            m_draggable = draggable;
            draggable->onBeginDrag(iniPoint, 
                                   m_hoverItemPart);
        }
        else
        {
            ASSERT(m_draggable);
            m_draggable->onDrag(currPoint);
            invalidate();
        }
    }
}

// ****************************************************************************
// Dealing with the left mouse button

void CMainView::OnLButtonDown(UINT nFlags, CPoint point)
{
    m_leftBtnDown = true;
    SetCapture();

    m_flags = nFlags; // We're gonna need those if this turns 
    m_point = point;  // out to be a single click

    bool selectionChanged = false;
    if(m_hoverItem && m_hoverItem->isSelectable())
    {
        selectionChanged = getUi().getSelectionManager().select(m_hoverItem);
    }
    
    if(m_leftBtnClickState != ClickCycleStates::IDLE)
    {
        // Double click but the mouse moved slightly

        ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        if(m_singleClickPossible)
        {
            KillTimer(TimerIDs::LBTN_DBL_CLK);
            onLSingleClick(nFlags, point);
        }
    }

    m_singleClickPossible = !selectionChanged; // Timer won't be started
    m_leftBtnClickState = ClickCycleStates::DOWN_RCVD;

    __super::OnLButtonDown(nFlags, point);
}

void CMainView::OnLButtonUp(UINT nFlags, CPoint point)
{
    m_leftBtnDown = false;
    ReleaseCapture();

    // TODO comment this
    if(m_leftBtnClickState == ClickCycleStates::DRAGGING)
    {
        Selectable* selection = getUi().getSelectionManager().getSelection();
        Node* selNode = dynamic_cast<Node*>(selection);
        //Node* hoverNode = HoverManager::instance().getSelectedNode();
        //if(selNode != NULL && hoverNode == NULL)
        //{
            //m_draggedBlock
                //AnimationManager::instance().performAnimatedInsertBlockAction(
                //  destBlock);
            //ActionManager::instance().performAction(ref<Action>(
            // new InsertBlockAction(m_draggedBlock)));
        //}
        ASSERT(m_draggable != NULL); // Otherwise the drag cycle wouldn't have started
        m_draggable->onEndDrag();
        invalidate();
        m_leftBtnClickState = ClickCycleStates::IDLE;
    }
    // if m_doubleClickTimerRunning_L is false, this is the first time we 
    // receive this message in the current cycle. So we start the timer which
    // will allow us to determine whether this is double click or single click
    else if(m_leftBtnClickState == ClickCycleStates::DOWN_RCVD)
    {
        m_leftBtnClickState = ClickCycleStates::FIRST_UP_RCVD;
        if(m_singleClickPossible)
        {
            SetTimer(TimerIDs::LBTN_DBL_CLK, ::GetDoubleClickTime(), NULL);
        }
    }
    // Otherwise it's the second time we receive this message, which means that
    // this is part of a double click process. Simply assert that the timer
    // has been correctly killed inside the double click handler
    else if(m_leftBtnClickState == ClickCycleStates::DBL_CLCK_RCVD)
    {
        KillTimer(TimerIDs::LBTN_DBL_CLK);
        m_leftBtnClickState = ClickCycleStates::IDLE;
    }
    __super::OnLButtonUp(nFlags, point);
}

void CMainView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // If we receive this message we are sure we have a double click,
    // so we don't need to keep the single-click detection timer running
    ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
    m_leftBtnClickState = ClickCycleStates::DBL_CLCK_RCVD;

    // Call the appropriate handler
    onLDoubleClick(nFlags, point);

    __super::OnLButtonDblClk(nFlags, point);
}

// Not really a mouse handler but that's what we use if for
void CMainView::OnTimer(UINT_PTR nIDEvent)
{
    if(nIDEvent == TimerIDs::LBTN_DBL_CLK)
    {
        KillTimer(nIDEvent);
        ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        m_leftBtnClickState = ClickCycleStates::IDLE;
        ASSERT(m_singleClickPossible);
        onLSingleClick(m_flags, m_point);
    }
    else if(nIDEvent == TimerIDs::RBTN_DBL_CLK)
    {
        KillTimer(nIDEvent);
        ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        m_rightBtnClickState = ClickCycleStates::IDLE;
        onRSingleClick(m_flags, m_point);
    }

    __super::OnTimer(nIDEvent);
}

//Node translateBlockCopy(const Node & block, int axis)
//{
// Node result = block;
//
// switch(axis)
// {
// case 1:
// case -1:
//  result.tile.x += axis;
//  break;
// case 2:
// case -2:
//  result.height += axis / 2;
//  break;
// case 3:
// case -3:
//  result.tile.y += axis / 3;
//  break;
// default:
//  assert(false);
// }
//
// return result;
//}

void CMainView::onLSingleClick(UINT /*nFlags*/, CPoint /*point*/)
{
    //if(SelectionManager::instance().isNodeSelected())
    //{
    // Node selectedBlock = SelectionManager::instance().getSelectedNode();

    // double dummy;
    // int clickedFace = selectedBlock.findFaceIntersection(
    //  computePickRay(m_point), dummy);
    // ASSERT(clickedFace != 0);

    // Node newBlock = translateBlockCopy(selectedBlock, clickedFace);
    // gx::Vec3 delta;
    // delta.x = newBlock.tile.x - selectedBlock.tile.x;
    // delta.y = newBlock.height - selectedBlock.height;
    // delta.z = newBlock.tile.y - selectedBlock.tile.y;

    // ref<Shape> finalShape = NodeManager::instance().getShape(selectedBlock)->clone();
    // finalShape->setPosition(finalShape->getPosition() + delta);
    // 
    // AnimationManager::instance().startBlockAnimation(selectedBlock, ref<Shape>(), finalShape, 200., 
    //  ref<Action>(new MoveNodeAction(selectedBlock, newBlock)));
    //}
}

void CMainView::onLDoubleClick(UINT nFlags, CPoint point)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    GeometryNode* selNode = dynamic_cast<GeometryNode*>(selection);
    if(selNode != NULL)
    {
        // Could use m_hoverFace here as well because dbl-click is very fast
        onBlockLDoubleClick(selNode, nFlags, point);
    }
    //else if(SelectionManager::instance().isTileSelected())
    //{
    // onTileLDoubleClick(SelectionManager::instance().getSelectedTile(), nFlags, point);
    //}
}

void CMainView::onBlockLDoubleClick(
        Node* clickedBlock,
        UINT nFlags, CPoint /*point*/)
{
    //// If ctrl is pressed => change shape geometry
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    // // TODO the name "cloneNextType" is horrible
    // ref<const Shape> oldShape = NodeManager::instance().getShape(clickedBlock);
    // ref<Shape> newShape = oldShape->cloneNextType();
    // 
    // ActionManager::instance().performAction(ref<Action>(
    //  new ReplaceShapeAction(clickedBlock, newShape)));

    //}
    //// If ctrl is NOT pressed => create a new copy of the selected shape 
    //// in the direction dictated by the selected face
    //else
    //{
    // Node newBlock = clickedBlock.extend(clickedFace);
    // ref<Shape> newShape = 
    //  NodeManager::instance().getShape(clickedBlock)->clone();
    // AnimationManager::instance().performAnimatedInsertBlockAction(
    //  newBlock, newShape);
    //}
    //invalidate();
}

//void CMainView::onTileLDoubleClick(
//        const Tile & clickedTile, UINT nFlags, CPoint /*point*/)
//{
    //// If ctrl is pressed, do nothing
    //// TODO create a random shape geometry
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    //}
    //// If ctrl is NOT pressed, create a new block at this tile
    //else
    //{
    // Node newBlock(clickedTile, 0);
    // ref<Shape> newShape(new CylinderShape);
    // AnimationManager::instance()
    //  .performAnimatedInsertBlockAction(newBlock, newShape);
    //}
    //invalidate();
//}

// ****************************************************************************


// ****************************************************************************
// Dealing with the middle mouse button

void CMainView::OnMButtonUp(UINT nFlags, CPoint point)
{
    m_middleBtnDown = false;
    ReleaseCapture();
    __super::OnMButtonUp(nFlags, point);
}

void CMainView::OnMButtonDown(UINT nFlags, CPoint point)
{
    m_middleBtnDown = true;
    m_point = point;
    m_iniCam = m_cameraMat;
    SetCapture();
    __super::OnMButtonDown(nFlags, point);
}

// ****************************************************************************


// ****************************************************************************
// Dealing with the right mouse button
// ****************************************************************************

void CMainView::OnRButtonDown(UINT nFlags, CPoint point)
{
/*
    m_flags = nFlags; // We're gonna need those if this turns 
    m_point = point;  // out to be a single click

    bool selectionChanged = 
        SelectionManager::instance().updateFrom(HoverManager::instance());

    if(m_rightBtnClickState != ClickCycleStates::IDLE)
    {
        ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        if(m_singleClickPossible)
        {
            KillTimer(TimerIDs::RBTN_DBL_CLK);
            onRSingleClick(nFlags, point);
        }
    }

    m_singleClickPossible = !selectionChanged;
    m_rightBtnClickState = ClickCycleStates::DOWN_RCVD;
*/
    __super::OnRButtonDown(nFlags, point);
}

void CMainView::OnRButtonUp(UINT nFlags, CPoint point)
{
    // if m_doubleClickTimerRunning_L is false, this is the first time we 
    // receive this message in the current cycle. So we start the timer which
    // will allow us to determine whether this is double click or single click
    if(m_rightBtnClickState == ClickCycleStates::DOWN_RCVD)
    {
        m_rightBtnClickState = ClickCycleStates::FIRST_UP_RCVD;
        if(m_singleClickPossible)
        {
            SetTimer(TimerIDs::RBTN_DBL_CLK, ::GetDoubleClickTime(), NULL);
        }
    }
    // Otherwise it's the second time we receive this message, which means that
    // this is part of a double click process. Simply assert that the timer
    // has been correctly killed inside the double click handler
    else if(m_rightBtnClickState == ClickCycleStates::DBL_CLCK_RCVD)
    {
        KillTimer(TimerIDs::RBTN_DBL_CLK);
        m_rightBtnClickState = ClickCycleStates::IDLE;
    }
    else
    {
        ASSERT(m_rightBtnClickState == ClickCycleStates::IDLE); 
    }

    __super::OnRButtonUp(nFlags, point);
}

void CMainView::OnRButtonDblClk(UINT nFlags, CPoint point)
{
    // If we receive this message we are sure we have a double click,
    // so we don't need to keep the single-click detection timer running
    ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
    m_rightBtnClickState = ClickCycleStates::DBL_CLCK_RCVD;

    // Call the appropriate handler
    onRDoubleClick(nFlags, point);

    __super::OnRButtonDblClk(nFlags, point);
}

void CMainView::onRSingleClick(UINT /*nFlags*/, CPoint point)
{
    // TODO do something about the context menu
    ClientToScreen(&point);
    OnContextMenu(this, point);
}

void CMainView::onRDoubleClick(UINT nFlags, CPoint point)
{
// if(SelectionManager::instance().isNodeSelected())
// {
//  onBlockRDoubleClick(SelectionManager::instance().getSelectedNode(), 
//   HoverManager::instance().getSelectedFace(), nFlags, point);
// }
// else if(SelectionManager::instance().isTileSelected())
// {
//  onTileRDoubleClick(SelectionManager::instance().getSelectedTile(), nFlags, point);
// }
}

void CMainView::onBlockRDoubleClick(const Node & clickedBlock, 
    int clickedFace, UINT nFlags, CPoint /*point*/)
{
    //ref<Shape> newShape = 
    // NodeManager::instance().getShape(clickedBlock)->clone();

    //// If ctrl is pressed => scale selected shape in the direction dictated 
    //// by the selected face
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    // newShape->scale(clickedFace);
    //}
    //// If ctrl is NOT pressed => rotate selected shape about the axis
    //// dictated by the selected face
    //else
    //{
    // newShape->rotate(clickedFace);
    //}

    //AnimationManager::instance().startBlockAnimation(
    // clickedBlock, ref<Shape>(), newShape, 300.,
    // ref<Action>(new ReplaceShapeAction(clickedBlock, newShape)));
}

//void CMainView::onTileRDoubleClick(
//        const Tile & /*clickedTile*/, UINT nFlags, CPoint /*point*/)
//{
//    // If ctrl is pressed, do nothing
//    if((nFlags & MK_CONTROL) == MK_CONTROL)
//    {
//    }
//    // If ctrl is NOT pressed, do nothing
//    else
//    {
//    }
//    //invalidate();
//}

// ****************************************************************************

// ****************************************************************************
// ****************************************************************************

void CMainView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    setKeyDown(nChar);
    __super::OnKeyDown(nChar, nRepCnt, nFlags);
}
 
void CMainView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    setKeyUp(nChar);
    __super::OnKeyUp(nChar, nRepCnt, nFlags);
}


const GLubyte stippleMask[17][128] =
{
  /* NOTE: 0% opaqueness is faster to set and probably faster to render with:
    glDisable(GL_POLYGON_STIPPLE);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); */
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},

  {0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},

  {0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55},

  {0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55},

  {0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77},

  {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77},

  /* NOTE: 100% opaqueness is faster to set and probably faster to render with:
        glDisable(GL_POLYGON_STIPPLE); */
  {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};

// ****************************************************************************
// Implementation of the functionality in the Edit menu
// ****************************************************************************

// TODO this should't be here
CString formatName(_T("WorldBuilderBlock"));

// IMPORTANT: OLE must be initialize to use the clipboard functionality
// See call to AfxOLEInit() in CAppClass::InitInstace()
// If we don't initialize OLE, trying to use the clipboard functions fails 

void CMainView::OnEditCopy()
{
    /*
    // Copy selected block to clipboard

    // 1. Create a new COleDataSource.
    // 2. Create a new CSharedFile (see initialization in example below).
    // 3. Write info to the CSharedFile instance.
    // 4. Detach the memory associated with the CSharedFile.
    // 5. Pass the detached memory and format info to CacheGlobalData().
    // 6. Place the data on the clipboard using SetClipboard().

    // ************************************************************************
    // Remove the current Clipboard contents. This involve opening it,
    // emptying it, and closing it again. Closing it is very important.
    // Otherwise, the call to SetClipboard below fails miserably beacuse 
    // it can't grab the clipboard if it's already opened by us)

    if(!OpenClipboard())
    {
        AfxMessageBox(_T("Cannot open the Clipboard"));
        return;
    }
    if(!EmptyClipboard())
    {
        AfxMessageBox(_T("Cannot empty the Clipboard"));
        return;
    }
    CloseClipboard();
    
    // ************************************************************************

    // ************************************************************************
    // Serialize To Clipboard

    CSharedFile sf(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT);
    CArchive ar(&sf, CArchive::store);

    // This would be the "standard" way to do it, but the way our Shape
    // hierarchy is designed, it doesn't play with the Serialization mechanism
    // (see how the function that reads a shape from a CArchive has to be static)
    // obj->Serialize(ar); 

    std::ostringstream oss;
    GeometryFactory::write(
        *NodeManager::instance().getShape(
            SelectionManager::instance().getSelectedNode()), oss);
    writeTextToArchive(oss.str(), ar);

    ar.Close();

    HGLOBAL hMem = sf.Detach();
    if(!hMem) 
    {
        AfxMessageBox(_T("Unexpected error"));
        return;
    }

    UINT format = RegisterClipboardFormat(formatName);
    if(format == 0)
    {
        AfxMessageBox(_T("Cannot register Clipboard format"));
        return;
    }

    COleDataSource * pSource = new COleDataSource;
    pSource->CacheGlobalData((CLIPFORMAT)format, hMem);
    pSource->SetClipboard();
    */
    // ************************************************************************
}

void CMainView::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
    // Enable the "copy" command only if there's a block selected
    GeometryNode* selNode = 
        dynamic_cast<GeometryNode*>(getUi().getSelectionManager().getSelection());
    pCmdUI->Enable(selNode != NULL);
}

void CMainView::OnEditPaste()
{
    /*
    // Copy block from clipboard to selected tile

    // TODO paste should work also when a face is selected!!

    // 1. Create a COleDataObject instance.
    // 2. Check to see if the format we want (Node) is available.
    // 3. Place the data associated with the clipboard into a CMemFile.
    // 4. Read the data out of the CMemFile.
    // 5. Release the global memory.

    COleDataObject obj;
    if (obj.AttachClipboard()) 
    {
        UINT format = RegisterClipboardFormat(formatName);
        if (obj.IsDataAvailable((CLIPFORMAT)format)) 
        {
            HGLOBAL hmem = obj.GetGlobalData((CLIPFORMAT)format);
            CMemFile sf((BYTE*) ::GlobalLock(hmem), ::GlobalSize(hmem));

            CArchive ar(&sf, CArchive::load);

            std::string wholeText = readCompleteTextArchive(ar);
            std::istringstream iss(wholeText);
            ref<Shape> shape = GeometryFactory::read(iss);

            ar.Close();

            ::GlobalUnlock(hmem);

            AnimationManager::instance().performAnimatedInsertBlockAction(
                    Node(SelectionManager::instance().getSelectedTile(), 0), shape);
        }
    }*/
}

void CMainView::OnUpdateEditPaste(CCmdUI *pCmdUI)
{
    /*bool shouldEnable = false;

    // Only enable "paste" command if a tile is selected and...
    if(SelectionManager::instance().isTileSelected())
    {
        // ...if a block object is currently in the clipboard
        COleDataObject obj;
        if (obj.AttachClipboard()) 
        {
            UINT format = RegisterClipboardFormat(formatName);
            shouldEnable = !!obj.IsDataAvailable((CLIPFORMAT)format);
        }
    }
    pCmdUI->Enable(shouldEnable);*/

}

void CMainView::OnEditUndo()
{
    getUi().getActionManager().undo();  // The manager takes care of everything
}

void CMainView::OnUpdateEditUndo(CCmdUI *pCmdUI)
{
    // Ask the doc if undo is possible
    pCmdUI->Enable(getUi().getActionManager().canUndo());
}

void CMainView::OnEditRedo()
{
    getUi().getActionManager().redo();  // The manager takes care of everything
}

void CMainView::OnUpdateEditRedo(CCmdUI *pCmdUI)
{
    // Ask the doc if redo is possible
    pCmdUI->Enable(getUi().getActionManager().canRedo());
}

void CMainView::OnEditCut()
{
    // Cup = copy + delete
    //OnEditCopy();
    //AnimationManager::instance().performAnimatedEraseBlockAction(
    //  SelectionManager::instance().getSelectedNode());
}

void CMainView::OnUpdateEditCut(CCmdUI *pCmdUI)
{
    // Cut is enabled if copy is enabled
    OnUpdateEditCopy(pCmdUI);
}

// ****************************************************************************

void CMainView::onSelectionChanged(SelectionManager* sender)
{
    ::PostMessage(*this, WM_ED3D_UPDATE_GIZMO_COMBOS, WPARAM(0), LPARAM(0));

    View3d::onSelectionChanged(sender);
}

LRESULT CMainView::OnUpdateGizmoCombos(WPARAM, LPARAM)
{
    updatePivotCombo();
    updateFrameCombo();
    return 0;
}

// ****************************************************************************

void CMainView::OnNodeDelete()
{
    //AnimationManager::instance().performAnimatedEraseBlockAction(
    // SelectionManager::instance().getSelectedNode());
}

void CMainView::addNodeTextureTransform(size_t layer)
{
    //Selectable* selection = getUi().getSelectionManager().getSelection();
    //GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
    //ASSERT(node);
    //node->addTextureTransform(layer, gx::Mat4(gx::eye));
}

void CMainView::removeNodeTextureTransform(size_t layer)
{
    //Selectable* selection = getUi().getSelectionManager().getSelection();
    //GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
    //ASSERT(node);
    //node->removeTextureTransform(layer);
}

void CMainView::selectNodeTextureTransform(size_t layer)
{
    //Selectable* selection = getUi().getSelectionManager().getSelection();
    //GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
    //ASSERT(node);
    //TextureTransform* tex = node->getTextureTransform(layer);
    //ASSERT(tex);
    //getUi().getSelectionManager().select(tex);
}

bool CMainView::nodeHasTextureTransform(size_t layer)
{
    //Selectable* selection = getUi().getSelectionManager().getSelection();
    //GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
    //ASSERT(node);
    //return node->getTextureTransform(layer) != NULL;
    return false;
}

#define MPG_ADD_TEXTURE_HANDLERS(layer)                                         \
void CMainView::OnNodeAddTextureTransform##layer()                              \
{                                                                               \
    addNodeTextureTransform(layer);                                             \
}                                                                               \
void CMainView::OnUpdateNodeAddTextureTransform##layer(CCmdUI* pCmdUI)          \
{                                                                               \
    pCmdUI->Enable(!nodeHasTextureTransform(layer));                            \
}                                                                               \
void CMainView::OnNodeRemoveTextureTransform##layer()                           \
{                                                                               \
    removeNodeTextureTransform(layer);                                          \
}                                                                               \
void CMainView::OnUpdateNodeRemoveTextureTransform##layer(CCmdUI* pCmdUI)       \
{                                                                               \
    pCmdUI->Enable(nodeHasTextureTransform(layer));                             \
}                                                                               \
void CMainView::OnNodeSelectTextureTransform##layer()                           \
{                                                                               \
    selectNodeTextureTransform(layer);                                          \
}                                                                               \
void CMainView::OnUpdateNodeSelectTextureTransform##layer(CCmdUI* pCmdUI)       \
{                                                                               \
    pCmdUI->Enable(nodeHasTextureTransform(layer));                             \
}   

MPG_ADD_TEXTURE_HANDLERS(0)
MPG_ADD_TEXTURE_HANDLERS(1)
MPG_ADD_TEXTURE_HANDLERS(2)
MPG_ADD_TEXTURE_HANDLERS(3)


#undef MPG_ADD_TEXTURE_HANDLERS

// :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :

void CMainView::onManipId(GizmoAxesType id)
{
    m_gizmoManager.setAxesType(id);
}

void CMainView::onUpdateManipId(CCmdUI *pCmdUI, GizmoAxesType id)
{
    pCmdUI->SetCheck(m_gizmoManager.getAxesType() == id);
}

void CMainView::OnManipCam()
{
    onManipId(GizmoAxesTypes::VIEW);
}

void CMainView::OnUpdateManipCam(CCmdUI *pCmdUI)
{
    onUpdateManipId(pCmdUI, GizmoAxesTypes::VIEW);
}

void CMainView::OnManipObj()
{
    onManipId(GizmoAxesTypes::NODE);
}

void CMainView::OnUpdateManipObj(CCmdUI *pCmdUI)
{
    onUpdateManipId(pCmdUI, GizmoAxesTypes::NODE);
}

// :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :

void CMainView::updatePivotCombo()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();

    btn->RemoveAllItems();

    GizmoPivotType pivotId = m_gizmoManager.getPivotType();
    Node* pivot = NULL;
    if(pivotId == GizmoPivotTypes::OTHER_NODE)
    {
        pivot = m_gizmoManager.getPivotNode();
        if(pivot != NULL)
        {
            std::wstring name = pivot->getName();
            CString str(name.empty() ? L"(unnamed)" : name.c_str());
            btn->AddItem(str, (DWORD)-1);
        }
    }
    
    btn->AddItem(L"Node origin", GizmoPivotTypes::NODE);
    btn->AddItem(L"World origin", GizmoPivotTypes::WORLD);
    btn->AddItem(L"Parent origin", GizmoPivotTypes::PARENT);

    Selectable* selection = getUi().getSelectionManager().getSelection();
    Node* selNode = dynamic_cast<Node*>(selection);
    if(selNode != NULL)
    {
        if(selNode != pivot)
        {
            btn->AddItem(L"Current selection", GizmoPivotTypes::OTHER_NODE);
        }
    }

    if(pivotId == GizmoPivotTypes::OTHER_NODE)
    {
        btn->SelectItem(0);
    }
    else
    {
        btn->SelectItem((int)pivotId);
    }
}

void CMainView::OnManipPivot()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();
    int sel = btn->GetCurSel();
    DWORD data = btn->GetItemData(sel);
    if(data >= GizmoPivotTypes::NODE && data <= GizmoPivotTypes::PARENT)
    {
        m_gizmoManager.setPivotNode(GizmoPivotType(data));
    }
    else
    {
        if(data == GizmoPivotTypes::OTHER_NODE)
        {
            Selectable* selection = getUi().getSelectionManager().getSelection();
            Node* selNode = dynamic_cast<Node*>(selection);
            ASSERT(selNode != NULL);
            m_gizmoManager.setPivotNode(selNode);
        }
        else
        {
            // Node name (first item) selected: do nothing
            ASSERT(data == -1);
            ASSERT(m_gizmoManager.getPivotType() == GizmoPivotTypes::OTHER_NODE);
        }
    }
    // Update the combo because the node name (first item)
    // might have to be removed
    updatePivotCombo();
}


void CMainView::OnUpdateManipPivot(CCmdUI *pCmdUI)
{
    // Initial update
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();
    if(btn->GetCount() == 0)
    {
        updatePivotCombo();
    }
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void CMainView::updateFrameCombo()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();

    btn->RemoveAllItems();

    GizmoAxesType frameId = m_gizmoManager.getAxesType();
    Node* frameNode = NULL;
    if(frameId == GizmoAxesTypes::OTHER_NODE)
    {
        frameNode = m_gizmoManager.getAxesNode();
        if(frameNode != NULL)
        {
            std::wstring name = frameNode->getName();
            CString str(name.empty() ? L"(unnamed)" : name.c_str());
            btn->AddItem(str, (DWORD)-1);
        }
    }
    
    btn->AddItem(L"Node frame", GizmoAxesTypes::NODE);
    btn->AddItem(L"World frame", GizmoAxesTypes::WORLD);
    btn->AddItem(L"Parent frame", GizmoAxesTypes::PARENT);
    btn->AddItem(L"View frame", GizmoAxesTypes::VIEW);

    Selectable* selection = getUi().getSelectionManager().getSelection();
    Node* selNode = dynamic_cast<Node*>(selection);
    if(selNode != NULL)
    {
        if(selNode != frameNode)
        {
            btn->AddItem(L"Current selection", GizmoAxesTypes::OTHER_NODE);
        }
    }

    if(frameId == GizmoAxesTypes::OTHER_NODE)
    {
        btn->SelectItem(0);
    }
    else
    {
        btn->SelectItem((int)frameId);
    }
}

void CMainView::OnManipFrame()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();
    int sel = btn->GetCurSel();
    DWORD data = btn->GetItemData(sel);
    if(data >= GizmoAxesTypes::NODE && data <= GizmoAxesTypes::VIEW)
    {
        m_gizmoManager.setAxesType(GizmoAxesType(data));
    }
    else
    {
        if(data == GizmoAxesTypes::OTHER_NODE)
        {
            Selectable* selection = getUi().getSelectionManager().getSelection();
            Node* selNode = dynamic_cast<Node*>(selection);
            ASSERT(selNode != NULL);
            m_gizmoManager.setAxesNode(selNode);
        }
        else
        {
            // Node name (first item) selected: do nothing
            ASSERT(data == -1);
            ASSERT(m_gizmoManager.getAxesType() == GizmoAxesTypes::OTHER_NODE);
        }
    }
    // Update the combo because the node name (first item)
    // might have to be removed
    updateFrameCombo();
}

void CMainView::OnUpdateManipFrame(CCmdUI *pCmdUI)
{
    // Initial update
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();
    if(btn->GetCount() == 0)
    {
        updateFrameCombo();
    }
}

void CMainView::OnManipComboCloseup()
{
    SetFocus();
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void CMainView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // --------------------------------------------------------------
    // We use the r, t and s keys to change the manipulator
    // --------------------------------------------------------------

    if(nChar == 's')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::SCALE);
        return;
    }
    if(nChar == 'r')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::ROTATE);
        return;
    }
    if(nChar == 't')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::TRANSLATE);
        return;
    }

    // --------------------------------------------------------------
    // We use the p/P keys to change the pivot
    // --------------------------------------------------------------

    if(nChar == 'p')
    {
        Selectable* selection = getUi().getSelectionManager().getSelection();
        Node* selNode = dynamic_cast<Node*>(selection);
        if(selNode != NULL)
        {
            m_gizmoManager.setPivotNode(selNode);
            updatePivotCombo();
        }
    }
    
    // Uppercase p => cycle through the standard "node" and "global" pivots
    else if(nChar == 'P')
    {
        m_gizmoManager.cyclePivot();
        updatePivotCombo();
    }

    __super::OnChar(nChar, nRepCnt, nFlags);
}

void CMainView::OnManipTransl()
{
    m_gizmoManager.setGizmoType(GizmoTypes::TRANSLATE);
}

void CMainView::OnUpdateManipTransl(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() == GizmoTypes::TRANSLATE);
}

void CMainView::OnManipRot()
{
    m_gizmoManager.setGizmoType(GizmoTypes::ROTATE);
}

void CMainView::OnUpdateManipRot(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() ==  GizmoTypes::ROTATE);
}

void CMainView::OnManipScale()
{
    m_gizmoManager.setGizmoType(GizmoTypes::SCALE);
}

void CMainView::OnUpdateManipScale(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() ==  GizmoTypes::SCALE);
}

void CMainView::onUiChanged(Ui* newUi)
{
    m_gizmoManager.setView(this);
}

void CMainView::onWorldChanged(World* newWorld)
{
    NodeManager& nm = newWorld->getNodeManager();
    for(NodeIterator it = nm.nodesBegin(); it != nm.nodesEnd(); ++it)
    {
        if((*it)->canBeDrawn())
        {
            addPickable(*it);
        }
    }
}

void CMainView::onNodeEvent(const NodeEvent& event_)
{
    Node* node = event_.node;

    switch(event_.type)
    {
    case NodeEvent::NodeAdded:
        if(node->canBeDrawn())
        {
            addPickable(node->getRef<Node>());
        }
        break;
    case NodeEvent::NodeRemoved:
        if(!node->canBeDrawn())
        {
            return;
        }
        removePickable(node->getRef<Node>());
        break;
    }

    View3d::onNodeEvent(event_);
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void CMainView::OnViewPerspective()
{
    if(getProjectionMode() == ProjectionModes::Perspective)
    {
        setProjectionMode(ProjectionModes::Orthographic);
    }
    else
    {
        setProjectionMode(ProjectionModes::Perspective);
    }
}

void CMainView::OnUpdateViewPerspective(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(getProjectionMode() == ProjectionModes::Perspective);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

//#include "Collada.h"

void CMainView::OnViewClear()
{
    //Selectable* sel = getUi().getSelectionManager().getSelection();
    //Node* node = dynamic_cast<Node*>(sel);
    //getWorld().getNodeManager().removeChildren(node);

    //FCollada::Initialize(); 
    //OpenColladaFile(L"knight.dae");
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void CMainView::OnNewEmptyShader()
{
    ShaderManager& sm = getWorld().getShaderManager();
    ref<ShaderProgram> shader(new ShaderProgram);
    std::wstring name = sm.generateNameFromPrefix(L"new");
    shader->setName(name);
    sm.registerShader(shader);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

namespace
{

// The file dialog filter for vertex scripts
const TCHAR vertFilter[] = _T("Vertex shaders (*.vert)|*.vert||");
const TCHAR fragFilter[] = _T("Fragment shaders (*.frag)|*.frag||");

}


void CMainView::OnLoadVertexShader()
{
    CFileDialog dlg(true, _T("*.vert"), NULL, 0, ::vertFilter);
    if(dlg.DoModal() != IDOK)
    {
        return;
    }

    // Convert filename from unicode to ansi
    CT2A ansiFileName(dlg.GetPathName());
    
    // Find selected shader program
    Selectable* sel = getUi().getSelectionManager().getSelection();
    ShaderProgram* shader = dynamic_cast<ShaderProgram*>(sel);
    ASSERT(shader != NULL);

    // Add new vertex shader to selected program
    shader->addVertexShader((LPCSTR)ansiFileName);
}


void CMainView::OnLoadFragmentShader()
{
    CFileDialog dlg(true, _T("*.frag"), NULL, 0, ::fragFilter);
    if(dlg.DoModal() != IDOK)
    {
        return;
    }

    // Convert filename from unicode to ansi
    CT2A ansiFileName(dlg.GetPathName());
    
    // Find selected shader program
    Selectable* sel = getUi().getSelectionManager().getSelection();
    ShaderProgram* shader = dynamic_cast<ShaderProgram*>(sel);
    ASSERT(shader != NULL);

    // Add new vertex shader to selected program
    shader->addFragmentShader((LPCSTR)ansiFileName);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

const char* shaderTemplate = 
    "                               \n \
    void main()                     \n \
    {                               \n \
                                    \n \
    }                               \n";


void CMainView::OnNewVertexShader()
{
    // Find selected shader program
    Selectable* sel = getUi().getSelectionManager().getSelection();
    ShaderProgram* shader = dynamic_cast<ShaderProgram*>(sel);
    ASSERT(shader != NULL);

    // Add new vertex shader to selected program
    std::istringstream iss(shaderTemplate);
    shader->addVertexShader(iss);
}

void CMainView::OnNewFragmentShader()
{
    // Find selected shader program
    Selectable* sel = getUi().getSelectionManager().getSelection();
    ShaderProgram* shader = dynamic_cast<ShaderProgram*>(sel);
    ASSERT(shader != NULL);

    // Add new vertex shader to selected program
    std::istringstream iss(shaderTemplate);
    shader->addFragmentShader(iss);
}


void CMainView::OnPrintUniformVariables()
{
    Selectable* sel = getUi().getSelectionManager().getSelection();
    ShaderProgram* prog = dynamic_cast<ShaderProgram*>(sel);
    ASSERT(prog != NULL);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

namespace
{

// The file dialog filter for vertex scripts
const TCHAR imagesFilter[] = _T("Image files (*.png; *.bmp)|*.png|*.bmp||");

}

void CMainView::OnAddTexture()
{
    CFileDialog dlg(true, _T(""), NULL, 0, ::imagesFilter);
    if(dlg.DoModal() != IDOK)
    {
        return;
    }

    // Convert filename from unicode to ansi
    CT2A ansiFileName(dlg.GetPathName());
    
    // Create texture, generate unique name for 
    // it, and register it with manager
    TextureManager& tm = getWorld().getTextureManager();
    ref<Texture> tex(new Texture((LPCSTR)ansiFileName));
    std::wstring name = tm.generateNameFromPrefix(L"newtex");
    tex->setName(name);
    tm.registerTexture(tex);
}


void CMainView::OnAddTextureLayerAfter()
{
    Selectable* sel = getUi().getSelectionManager().getSelection();
    TextureLayer* texLayer = dynamic_cast<TextureLayer*>(sel);
    ASSERT(texLayer);

    Material* mat = texLayer->getMaterial();
    mat->addTextureLayerAfter(
        texLayer, 
        getWorld().getTextureManager().getDefaultTexture(),
        EnvironmentModes::Replace);  
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
